﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.AddIn;
using RootfusAddinView;
using ROOTNET.Interface;
using System.Xml.Serialization;
using System.Xml;
using System.IO;

namespace RootfusBuiltinAddins.TFile
{
    [AddIn("TFile")]
    public class TFileAddIn : RootfusSurfaceItem
    {
        /// <summary>
        /// Dummy for development purposes...
        /// </summary>
        class TROOTObjectPin
        {
            string _name;
            List<NTKey> _keys = new List<NTKey>();

            public TROOTObjectPin(string name)
            {
                _name = name;
            }
            public string Name
            {
                get { return _name; }
            }
            public List<NTKey> Keys
            {
                get { return _keys; }
            }

            private NTObject _cache = null;

            public NTObject ObjectCache
            {
                get { return _cache; }
                set
                {
                    _cache = value;
                    _cache_valid = true;
                }
            }

            private bool _cache_valid = false;

            /// <summary>
            /// Get or set the cache being valid.
            /// </summary>
            public bool CacheValid
            {
                get { return _cache_valid; }
                set { _cache_valid = value; }
            }
        }

        Dictionary<string, TROOTObjectPin> _outputs = new Dictionary<string, TROOTObjectPin>();

        /// <summary>
        /// Get everything setup and working. In particular, fill in the # of pins we have around.
        /// </summary>
        public TFileAddIn()
        {
        }

        /// <summary>
        /// List of files...
        /// </summary>
        private List<NTFile> _current_files = new List<NTFile>();

        /// <summary>
        /// The list of files that we would like to be opened.
        /// </summary>
        private ObservableCollection<string> _requested_files_to_open = new ObservableCollection<string>();

        /// <summary>
        /// Tries to make the requested_files_to_open look like the regular files to open...
        /// </summary>
        private void SyncFileLists()
        {
            bool changes = false;

            ///
            /// First, close any files that we don't care about any longer
            /// 

            var oldFiles = from item in _current_files
                           where !_requested_files_to_open.Contains(item.GetName())
                           select item;
            foreach (var f in oldFiles.ToArray())
            {
                DisconnectFromFile(f);
                changes = true;
            }

            ///
            /// Now, get a list of files to open!
            /// 

            var nowOpenFiles = from item in _current_files
                               select item.GetName();

            var newFiles = from req_file in _requested_files_to_open
                           where !nowOpenFiles.Contains(req_file)
                           select req_file;
            foreach (var f in newFiles)
            {
                ConnectToFile(f);
                changes = true;
            }

            ///
            /// Let everyone downstream of us know that things have changed... Invalidate
            /// any object caches. Hopefully no one is holding onto this object native pointer
            /// somewhere else in a way that it needs to have it...
            /// 

            if (changes)
            {
                foreach (var p in _outputs.Values)
                {
                    p.CacheValid = false;
                }
            }

            ///
            /// Update the info that we need to save about ourselves -- as our config has changed.
            /// 

            if (changes)
            {
                UpdateSaveInfo();
            }

            ///
            /// Finally, update the UI to show we have open files...
            /// 

            UpdateUI();

            ///
            /// Tail call to others to update their UI if they need to because we changed!
            /// 

            if (changes)
            {
                Callback.OutputPinsChanged();
            }
        }

        /// <summary>
        /// We are going to remove a file from everything we know.
        /// </summary>
        /// <param name="f"></param>
        private void DisconnectFromFile(NTFile f)
        {
            ///
            /// First is to make sure we aren't keeping any keys around for the file.
            /// 

            int index = _current_files.IndexOf(f);
            if (index < 0)
            {
                return; // This is a nasty bug! We should really die here, dead to rights!
            }
            foreach (var k in _outputs.Values)
            {
                TROOTObjectPin p = k as TROOTObjectPin;
                p.Keys.RemoveAt(index);
            }

            ///
            /// Wipe out the file from the list.
            /// 

            _current_files.RemoveAt(index);

            ///
            /// If we are any histograms that don't have anything here...
            /// 

            var emptys = from c in _outputs.Values
                         where c.Keys.Count == 0
                         select c;

            foreach (var item in emptys.ToArray())
            {
                _outputs.Remove(item.Name);
            }
        }

        private static XmlSerializer _ser = null;

        private XmlSerializer FSer
        {
            get
            {
                if (_ser == null)
                {
                    _ser = new XmlSerializer(typeof(ObservableCollection<string>));
                }
                return _ser;
            }
        }

        /// <summary>
        /// We have changed our config -- send it along.
        /// </summary>
        private void UpdateSaveInfo()
        {
            ///
            /// Turn our settings into XML
            /// 

            StringBuilder bld = new StringBuilder();

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;
            settings.Indent = false;
            XmlWriter writer = XmlWriter.Create(bld, settings);
            FSer.Serialize(writer, _requested_files_to_open);
            Callback.UpdateSaveInformation(bld.ToString());
        }

        /// <summary>
        /// We need to flush out what we know, and load up something else!
        /// </summary>
        /// <param name="xml_config"></param>
        public override void SetConfigXML(string xml_config)
        {
            _requested_files_to_open = FSer.Deserialize(new StringReader(xml_config)) as ObservableCollection<string>;

            ///
            /// Ok -- make the request reflect reality.
            /// 

            SyncFileLists();
        }

        /// <summary>
        /// Update the UI to reflect how many files we have open, etc.
        /// </summary>
        private void UpdateUI()
        {
            string text = "<no files>";
            if (_requested_files_to_open.Count > 0)
            {
                text = _requested_files_to_open[0];
                text = text.Substring(text.LastIndexOf("\\") + 1);
                text = text.Substring(0, text.IndexOf("."));
            }
            if (_requested_files_to_open.Count > 1)
            {
                text += ",...(" + _requested_files_to_open.Count + " files)";
            }

            InitSurfaceUI().lbFileName.Content = text;

            ///
            /// Finally, sort the list of pins so anyone that wants the complete list gets something
            /// that is sensible.
            /// 

            _output_pins.Clear();
            foreach (var item in _outputs.Keys)
            {
                _output_pins.Add(item);
            }
            _output_pins.Sort();
        }

        /// <summary>
        /// Load up a particular file.
        /// </summary>
        /// <param name="p"></param>
        private void ConnectToFile(string p)
        {
            ///
            /// Open the file. We have to keep it held, otherwise GC will come along and try to clean
            /// it up (as it should) and we will loose all of our references to it. Yes, this is a memory
            /// hog. Sorry.
            ///

            NTFile f = new ROOTNET.NTFile(p, "READ");

            ///
            /// Create a quick look up for all the pins that have been previously defined.
            /// 

            CreatePinsForDirectory(f);


            ///
            /// Finally, for all the guys that weren't in this file, but we already have keys for, we need to put in a
            /// null -- the # of keys must remain in sync! :-)
            /// 

            var two_few_list = from item in _outputs.Values
                               where (item as TROOTObjectPin).Keys.Count <= _current_files.Count
                               select (item as TROOTObjectPin);
            foreach (var pin in two_few_list)
            {
                pin.Keys.Add(null);
            }

            ///
            /// Finally, add it to the list of files we are tracking...
            /// 

            _current_files.Add(f);
        }

        /// <summary>
        /// Create pin entries -- with no prefix in the name.
        /// </summary>
        /// <param name="d"></param>
        /// <param name="pinLookup"></param>
        private void CreatePinsForDirectory(NTDirectory d)
        {
            CreatePinsForDirectory(d, "");
        }

        /// <summary>
        /// For a given directory, create all the output pins matching histograms, etc.
        /// </summary>
        /// <param name="f"></param>
        private void CreatePinsForDirectory(NTDirectory d, string prefix)
        {
            NTList items = d.GetListOfKeys();
            for (int i = 0; i < items.GetEntries(); i++)
            {
                NTKey k = items.At(i) as NTKey;
                if (!IsDirectoryObject(k))
                {
                    string pname = prefix + k.GetName();
                    if (!_outputs.ContainsKey(pname))
                    {
                        TROOTObjectPin newp = new TROOTObjectPin(pname);
                        for (int index = 0; i < _current_files.Count; i++)
                        {
                            newp.Keys.Add(null); // Add in dummies -- because we didn't have any yet!
                        }
                        _outputs[pname] = newp;
                    }
                    _outputs[pname].Keys.Add(k);
                }
                else
                {
                    CreatePinsForDirectory(k.ReadObj() as NTDirectory, prefix + k.GetName() + "/");
                }
            }
        }

        /// <summary>
        /// Returns true if the object this key points to is from TDirectory.
        /// </summary>
        /// <param name="k"></param>
        /// <returns></returns>
        private bool IsDirectoryObject(NTKey k)
        {
            NTClass c = ROOTNET.NTROOT.gROOT.GetClass(k.GetClassName());
            if (c == null)
            {
                return false;
            }
            return c.InheritsFrom("TDirectory");
        }

        /// <summary>
        /// Track the UW so we don't build too many of them!
        /// </summary>
        TFileUINormal _ui = null;

        /// <summary>
        /// Return the UI.
        /// </summary>
        /// <returns></returns>
        public override System.Windows.FrameworkElement SurfaceUI
        {
            get
            {
                return InitSurfaceUI();
            }
        }

        /// <summary>
        /// make sure the UI is created.
        /// </summary>
        /// <returns></returns>
        private TFileUINormal InitSurfaceUI()
        {
            if (_ui == null)
            {
                _ui = new TFileUINormal();
            }
            return _ui;
        }

        /// <summary>
        /// make sure the UI is created.
        /// </summary>
        /// <returns></returns>
        private TFileUIConfig InitConfigUI()
        {
            TFileUIConfig result = new TFileUIConfig();

            ///
            /// Update the list of files that we have open...
            /// 

            result.ROOTFileList = _requested_files_to_open;

            return result;
        }

        /// <summary>
        /// The config is finished being shown. Update the world!
        /// </summary>
        public override void ConfigDone()
        {
            SyncFileLists();
        }

        /// <summary>
        /// Let them know we can do settings!
        /// </summary>
        public override bool HasSettingsUI
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the user control that does the settings.
        /// </summary>
        public override System.Windows.FrameworkElement ConfigUI
        {
            get { return InitConfigUI(); }
        }

        List<PinDefinition> _input_pins = new List<PinDefinition>();
        List<string> _output_pins = new List<string>();

        /// <summary>
        /// This file guy has no input pins. Makes it easy on the UI. :-)
        /// </summary>
        public override IList<PinDefinition> InputPins
        {
            get { return _input_pins; }
        }

        public override IList<string> OutputPinNames
        {
            get { return _output_pins; }
        }

        /// <summary>
        /// We have no updates, so this is a null function -- we never need to update ourselves
        /// except when our input file changes!
        /// </summary>
        public override void Update()
        {
        }

        /// <summary>
        /// Hey -- they want something we have! Cool! Return it!
        /// </summary>
        /// <param name="pin"></param>
        /// <returns></returns>
        public override NTObject GetPinDataAsTObject(string pin_in)
        {
            ///
            /// If this conversion doesn't work - then we are in a world of hurt!
            ///

            if (!_outputs.ContainsKey(pin_in))
            {
                return null;
            }
            TROOTObjectPin pin = _outputs[pin_in];

            ///
            /// If there are no keys, get the hell out of here!
            /// 

            if (pin.Keys.Count == 0)
            {
                return null;
            }

            ///
            /// If the cache is good -- then use it!
            /// 

            if (pin.CacheValid)
            {
                return pin.ObjectCache;
            }

            ///
            /// Great. Now that there are keys, if there is one key we have it made!
            /// One tricky bit - if we are getting called with an old pin data (from a previous file)
            /// then in all likelyhood this next step is going to fail (as it should), and we will have zero
            /// items.
            /// 

            var non_null_list = from item in pin.Keys
                                where item != null
                                select item;

            int count = non_null_list.Count();
            if (count == 0)
            {
                pin.ObjectCache = null;
                return null;
            }
            if (count == 1)
            {
                pin.ObjectCache = non_null_list.ToArray()[0].ReadObj();
                return pin.ObjectCache;
            }

            ///
            /// Creap. We have to add them together. Sucks to be us!
            /// 

            var objs = from item in non_null_list
                       select item.ReadObj();

            var objlist = objs.ToArray();
            NTClass obj = objlist[0].IsA();
            if (obj.InheritsFrom("TH1"))
            {
                pin.ObjectCache = AddTH1(objlist);
            }
            else if (obj.InheritsFrom("TTree"))
            {
                pin.ObjectCache = AddTTree(objlist);
            }
            else
            {
                /// Bail out for now.
                pin.ObjectCache = null;
            }
            pin.CacheValid = true;
            return pin.ObjectCache;
        }

        /// <summary>
        /// Add this histogram together. But make a copy... :-)
        /// </summary>
        /// <param name="objlist"></param>
        /// <returns></returns>
        private NTObject AddTH1(NTObject[] objlist)
        {
            NTH1 b = null;
            foreach (NTH1 h in objlist)
            {
                if (b == null)
                {
                    b = h.Clone() as NTH1;
                }
                else
                {
                    b.Add(h);
                }
            }
            return b;
        }

        /// <summary>
        /// Actually -- no idea what to do here! Bye for now!
        /// </summary>
        /// <param name="objlist"></param>
        /// <returns></returns>
        private NTObject AddTTree(NTObject[] objlist)
        {
            return null;
        }
    }
}
